/**
 * 物品查看器 - 点击场景中的一个可查看物品，将物品放大到镜头前，并能单独旋转物体进行查看。
 *
 * 功能
 *   - 多层级考虑: 点击子层级物体也可以查看此物体。
 *   - 新场景: 在新场景中显示物体，就不会被墙遮挡。
 *   - 替换控制器: 显示时禁用原有控制器，启用内置的 OrbitControls。
 * [ ] 查看变换: 允许对查看的物体进行旋转
 * [ ] 查看替换: 允许点击一个物体后，查看另一个物体。以实现点击展柜，显示展品的功能。
 */

import { THREE, Stats, OrbitControls, gsap, utils } from '@/scripts/three/toolchain'
import TourControls from './TourControls'

const VIEWING_DISTANCE = 1.0
const VIEWING_SIZE = 1.0
const LAYER = 1

type TargetObjectInfo = { x?: number; y?: number; z?: number }

export default class ObjectViewer {
  // 主属性
  scene = new THREE.Scene() // 新建的场景
  viewingCamera: THREE.PerspectiveCamera // 查看物品用的相机
  controls: OrbitControls // 查看物品用的控制器
  alwaysOnTop = true // 是否会被其他模型遮挡
  targetGroup: THREE.Group // 要拾取点位所在的目标物体的容器
  targetObjectMap: { [name: string]: TargetObjectInfo } = {} // 可以被查看的物体的名称
  targetObjectNames: string[] = [] // 可以被查看的物体的名称
  // 绑定对象
  renderer: THREE.WebGLRenderer // 绑定的渲染器
  camera: THREE.PerspectiveCamera // 绑定的相机
  extraControls: TourControls // 其他控制器
  viewGroup = new THREE.Group() // 被查看物体的克隆体的容器
  constructor(
    renderer: THREE.WebGLRenderer,
    camera: THREE.PerspectiveCamera,
    targetGroup: THREE.Group,
    targetObjectMap: { [name: string]: TargetObjectInfo },
    extraControls: TourControls,
  ) {
    this.renderer = renderer
    this.camera = camera
    this.targetGroup = targetGroup
    this.targetObjectMap = targetObjectMap
    this.targetObjectNames = Object.keys(targetObjectMap)
    this.onMouseDown = this._onMouseDown.bind(this)
    this.onMouseUp = this._onMouseUp.bind(this)
    // this.createHelper()
    this.extraControls = extraControls
    this.scene.add(this.viewGroup)
    // 内部相机
    const aspect = document.body.clientWidth / document.body.clientHeight
    this.viewingCamera = new THREE.PerspectiveCamera(60, aspect, 0.1, 1000)
    this.viewingCamera.position.z = VIEWING_DISTANCE
    window.addEventListener('resize', () => {
      const width = document.body.clientWidth
      const height = document.body.clientHeight
      this.viewingCamera.aspect = width / height
      this.viewingCamera.updateProjectionMatrix()
    })

    // 控制器
    const controls = new OrbitControls(this.viewingCamera, renderer.domElement)
    controls.enabled = false
    controls.enableDamping = true
    controls.dampingFactor = 0.15
    controls.maxDistance = 3
    controls.autoRotate = true
    this.controls = controls

    // 环境光源
    const ambientLight = new THREE.AmbientLight()
    ambientLight.intensity = 0.75 // 降低亮度
    this.scene.add(ambientLight)
    // 点光源-右前暖
    const pointLight = new THREE.PointLight(0xfffff8, 1)
    pointLight.position.set(10, 4, 10)
    pointLight.distance = 50
    this.scene.add(pointLight)
    // 点光源-正后冷
    const pointLight2 = new THREE.PointLight(0xf8f8ff, 0.5)
    pointLight2.position.set(0, 4, -10)
    pointLight2.distance = 50
    this.scene.add(pointLight2)
    // 平行光源
    const directionalLight = new THREE.DirectionalLight()
    directionalLight.position.set(1, 5, -1)
    directionalLight.intensity = 0.3
    this.scene.add(directionalLight)
  }

  // 启用、禁用
  enabled = false
  enable() {
    this.renderer.domElement.addEventListener('mousedown', this.onMouseDown)
    this.renderer.domElement.addEventListener('mouseup', this.onMouseUp)
    this.enabled = true

    this.setupLayers()
  }
  disable() {
    this.renderer.domElement.removeEventListener('mousedown', this.onMouseDown)
    this.renderer.domElement.removeEventListener('mouseup', this.onMouseUp)
    this.enabled = false
  }

  // 渲染
  render() {
    if (this.alwaysOnTop) this.renderer.clearDepth()
    this.renderer.render(this.scene, this.viewingCamera)
  }

  // 拾取
  pointer = new THREE.Vector2()
  raycaster = new THREE.Raycaster()
  mouseDownAt = new THREE.Vector2()
  mouseUpAt = new THREE.Vector2()
  onMouseDown: (e: MouseEvent) => void
  onMouseUp: (e: MouseEvent) => void
  _onMouseDown(e: MouseEvent) {
    this.mouseDownAt.set(e.clientX, e.clientY)
  }
  _onMouseUp(e: MouseEvent) {
    this.mouseUpAt.set(e.clientX, e.clientY)
    if (this.mouseDownAt.distanceTo(this.mouseUpAt) > 5) return

    if (this.viewingObject) {
      this.exitView()
      return
    }

    this.pointer.set(
      (e.clientX / window.innerWidth) * 2 - 1,
      1 - (e.clientY / window.innerHeight) * 2,
    )
    this.raycaster.setFromCamera(this.pointer, this.camera)
    const intersects = this.raycaster.intersectObject(this.targetGroup, true)
    if (intersects.length > 0) {
      const intersectObject = intersects[0].object
      console.log('[ObjectViewer] 点击了物体', intersectObject)
      const foundViewObject = this.pickViewObject(intersectObject)
      console.log('[ObjectViewer] foundViewObject', foundViewObject)
      if (foundViewObject) {
        // console.log(`[ObjectViewer] 该物体可查看`)
        this.viewObject(foundViewObject)
      } else {
        console.log('[ObjectViewer] 点击的物体不是可查看的')
      }
    } else {
      console.log('[ObjectViewer] 未拾取到点')
    }
  }

  // 检查点击到的物体是不是可查看物体，或者其一部分，是的话返回可查看物体
  pickedObjectName = ''
  pickViewObject(node: THREE.Object3D) {
    if (this.targetObjectNames.includes(node.name)) {
      this.pickedObjectName = node.name
      return node
    }
    while (node.parent) {
      node = node.parent
      if (this.targetObjectNames.includes(node.name)) {
        this.pickedObjectName = node.name
        return node
      }
    }
    return null
  }

  // 查看物体
  viewingObject: THREE.Object3D | null = null
  viewObject(object: THREE.Object3D) {
    this.viewingObject = this.cloneAndWrapObject(object)
    const objectInfo = this.targetObjectMap[this.pickedObjectName]
    if (objectInfo) {
      this.viewingObject.rotation.x = THREE.MathUtils.degToRad(objectInfo.x || 0)
      this.viewingObject.rotation.y = THREE.MathUtils.degToRad(objectInfo.y || 0)
      this.viewingObject.rotation.z = THREE.MathUtils.degToRad(objectInfo.z || 0)
      console.log('objectInfo', objectInfo)
    }
    this.viewGroup.add(this.viewingObject)
    this.extraControls.enabled = false
    this.controls.enabled = true
  }
  exitView() {
    this.viewGroup.clear()
    this.viewingObject = null
    this.extraControls.enabled = true
    this.controls.enabled = false
  }

  // 克隆并包装物体
  cloneAndWrapObject(object: THREE.Object3D) {
    const clonedObject = object.clone()
    const box3 = new THREE.Box3()
    box3.setFromObject(clonedObject)
    const pos = new THREE.Vector3()
    box3.getCenter(pos)
    // console.log('pos', pos)
    const size = new THREE.Vector3()
    box3.getSize(size)
    // console.log('size', size)
    const radius = Math.hypot(size.x, size.y, size.z)
    // console.log('radius', radius)
    const scale = VIEWING_SIZE / radius
    clonedObject.position.copy(pos.negate().multiplyScalar(scale))
    clonedObject.scale.setScalar(scale)
    const group = new THREE.Group()
    group.add(clonedObject)
    return group
  }

  setupLayers() {
    // 为可见物体启用层1，不可见物体禁用层1
    this.targetGroup.traverse((_node) => {
      const node = _node as THREE.Mesh
      if (node.isMesh) {
        if (node.visible && utils.isAllAncestorVisible(node)) node.layers.enable(LAYER)
        else node.layers.disable(LAYER)
      }
    })
    // 设置光线投掷器的层
    this.raycaster.layers.set(LAYER)
  }

  update() {
    // if (this.viewingObject) this.viewingObject.rotation.y += 0.02
    this.controls.update()
  }
}
